/***********************************************************************************************************************
 * ############################################# Application AdewebLite #############################################
 * AdewebLite Android est une application permettant de consulter des emplois du temps fournis par l'université UHP
 * avec ou sans connexion internet (informations gardées en mémoire à chaque synchronisation). Et utilisant la web
 * API ADE pour collecter les données.
 * @author Andrianjafintrimo Victor, Zanette Sébastien
 * 
 * Basée sur l'application du même nom, utilisant le flux RSS (données à traiter), de :
 * @author Schnell Michaël, Zanette Sébastien
 * **********************************************************************************************************************
 */

package fr.ul.ade.prime.model;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.provider.BaseColumns;
import fr.ul.ade.prime.exception.AAAException;
import fr.ul.ade.prime.provider.AdeWebContract;
import fr.ul.ade.prime.util.Lists;

public abstract class ScheduleUpdater {

	@SuppressWarnings("unused")
	private final static String TAG = ScheduleUpdater.class.getSimpleName();

	protected ArrayList<Lesson> insert; // Nouveaux cours à inserer

	protected ArrayList<Lesson> update; // Cours modifiés à modifier

	protected ArrayList<Lesson> delete; // Cours à supprimer

	protected ArrayList<Lesson> recent; // Cours récents modifiés

	protected static final GregorianCalendar calendar = new GregorianCalendar();

	// protected boolean importantChange;// Indique si un cours du futur proche
	// // à été modifé/inséré/supprimé

	public static final int CLOSE_INTERVAL = 2;

	/**
	 * Initialise <code>ScheduleUpdater</code>
	 */
	protected ScheduleUpdater() {
		super();
		this.insert = Lists.newArrayList();
		this.update = Lists.newArrayList();
		this.delete = Lists.newArrayList();
		this.recent = Lists.newArrayList();
		// this.importantChange = false;
	}// ScheduleUpdater()

	/**
	 * Indique si la date donnée se trouve dans les 2 jours qui suivent.
	 * 
	 * <p>
	 * Exemple: date = {@code "05/06/2011"}<br>
	 * Si date du jours = {@code "01/06/2011"}, alors la fonction retourne
	 * {@code false}<br>
	 * Si date du jours = {@code "04/06/2011"}, alors la fonction retourne
	 * {@code true}
	 * 
	 * @param date
	 *            date à vérifier
	 * @return {@code true} si la date donnée se trouve dans les 2 jours qui
	 *         suivent, {@code false} sinon
	 */
	protected static boolean isClose(Date date) {
		Date now = calendar.getTime();
		// Log.d(TAG, String.format("[isClose] (%s), now: %s",
		// date, now)) ;
		int nowDayYear = calendar.get(Calendar.DAY_OF_YEAR);

		calendar.setTime(date);
		int dayYear = calendar.get(Calendar.DAY_OF_YEAR);

		// Log.d(TAG, String.format("   nowDay: %d, day: %d (%d)",
		// nowDayYear, dayYear, dayYear + 365)) ;
		if (dayYear < nowDayYear) {
			dayYear += 365;
		}// if

		boolean close = (dayYear - nowDayYear <= CLOSE_INTERVAL);
		calendar.setTime(now);
		// Log.d(TAG, String.format("   >>> after: %s", calendar.getTime())) ;

		return close;
	}// isClose(Date)

	/**
	 * Initialise les champs liste dans lesquel on stoquera les cours triés
	 * (nouveau, modifié, supprimé).
	 */
	protected void initialize() {
		this.insert.clear();
		this.update.clear();
		this.delete.clear();
		this.recent.clear();
		// this.importantChange = false;
	}// initialize()

	/**
	 * Comparaison de 2 emplois du temps.
	 * 
	 * @param oldS
	 *            ancien emploi du temps
	 * @param newS
	 *            emploi du temps récupéré
	 */
	public void compare(Schedule oldS, Schedule newS) {
		this.initialize();
		this.compareS(oldS, newS);
	}// compare(Schedule, Schedule)

	/**
	 * Comparaison de 2 emplois du temps.
	 * 
	 * @param oldS
	 * @param newS
	 */
	protected abstract void compareS(Schedule oldS, Schedule newS);

	/**
	 * Insère dans la base de données les cours de la liste de nouveaux cours.
	 * 
	 * @throws AAAException
	 */
	public void insert(Context context) throws AAAException {
		if (this.insert.isEmpty()) {
			return;
		}// if

		Uri lessonUri = AdeWebContract.Lesson.CONTENT_URI;
		ContentValues[] allValues = new ContentValues[this.insert.size()];
		int i = 0;
		for (Lesson lesson : this.insert) {
			ContentValues values = new ContentValues();
			values.put(AdeWebContract.Lesson.LESSON_TITLE, lesson.getTitle());
			values.put(AdeWebContract.Lesson.LESSON_DATE, lesson.getDate());
			values.put(AdeWebContract.Lesson.LESSON_START_TIME,
					lesson.getStartHour());
			values.put(AdeWebContract.Lesson.LESSON_END_TIME,
					lesson.getEndHour());
			values.put(AdeWebContract.Lesson.LESSON_SCHEDULE,
					Long.valueOf(lesson.getScheduleId()));
			values.put(AdeWebContract.Lesson.LESSON_TYPE,
					Integer.valueOf(lesson.getType().ordinal()));
			values.put(AdeWebContract.Lesson.LESSON_CHANGE,
					Integer.valueOf(lesson.getChange()));
			values.put(AdeWebContract.Lesson.LESSON_ROOMS, lesson.getRoom());
			values.put(AdeWebContract.Lesson.LESSON_GROUPS, lesson.getGroups());
			values.put(AdeWebContract.Lesson.LESSON_TEACHERS,
					lesson.getTeacher());
			values.put(AdeWebContract.Lesson.LESSON_COLOR, lesson.getColor());
			values.put(AdeWebContract.Lesson.LESSON_NOTE, lesson.getNote());
			values.put(AdeWebContract.Lesson.LESSON_GUID, lesson.getGuid());
			values.put(AdeWebContract.Lesson.LESSON_DELETED,
					Integer.valueOf(lesson.isDeleted() ? 1 : 0));
			// values.put(AdeWebContract.Lesson.LESSON_LAST_UPDATE, (String)
			// null) ;

			allValues[i] = values;
			i++;
		}// for

		context.getContentResolver().bulkInsert(lessonUri, allValues);
	}// insert()

	/**
	 * Met à jour dans la base de données les cours de la liste de cours
	 * modifiés.
	 * 
	 * @throws AAAException
	 */
	public void update(Context context) throws AAAException {
		for (Lesson lesson : this.update) {
			Uri lessonUri = ContentUris
					.withAppendedId(AdeWebContract.Lesson.CONTENT_URI,
							lesson.getLessonId());

			ContentValues values = new ContentValues();
			// Mise à jour du cours
			// values.put(AdeWebContract.Lesson._ID,
			// Long.valueOf(lesson.getLessonId()));
			values.put(AdeWebContract.Lesson.LESSON_TITLE, lesson.getTitle());
			values.put(AdeWebContract.Lesson.LESSON_DATE, lesson.getDate());
			values.put(AdeWebContract.Lesson.LESSON_START_TIME,
					lesson.getStartHour());
			values.put(AdeWebContract.Lesson.LESSON_END_TIME,
					lesson.getEndHour());
			values.put(AdeWebContract.Lesson.LESSON_TYPE,
					Integer.valueOf(lesson.getType().ordinal()));
			values.put(AdeWebContract.Lesson.LESSON_CHANGE,
					Integer.valueOf(lesson.getChange()));
			values.put(AdeWebContract.Lesson.LESSON_ROOMS, lesson.getRoom());
			values.put(AdeWebContract.Lesson.LESSON_GROUPS, lesson.getGroups());
			values.put(AdeWebContract.Lesson.LESSON_TEACHERS,
					lesson.getTeacher());
			values.put(AdeWebContract.Lesson.LESSON_COLOR, lesson.getColor());
			values.put(AdeWebContract.Lesson.LESSON_NOTE, lesson.getNote());
			values.put(AdeWebContract.Lesson.LESSON_GUID, lesson.getGuid());
			values.put(AdeWebContract.Lesson.LESSON_DELETED,
					Integer.valueOf(lesson.isDeleted() ? 1 : 0));
			// Mise à jour de l'historique
			LessonHistory history = lesson.getHistory();
			String date, startHour, endHour, rooms;
			date = startHour = endHour = rooms = null;
			if (history != null) {
				date = history.getDate();
				startHour = history.getStartHour();
				endHour = history.getEndHour();
				rooms = history.getRooms();
			}// if
			values.put(AdeWebContract.Lesson.LESSON_HISTORY_DATE, date);
			values.put(AdeWebContract.Lesson.LESSON_HISTORY_START_TIME,
					startHour);
			values.put(AdeWebContract.Lesson.LESSON_HISTORY_END_TIME, endHour);
			values.put(AdeWebContract.Lesson.LESSON_HISTORY_ROOMS, rooms);

			context.getContentResolver().update(lessonUri, values, null, null);
		}// for
	}// update()

	/**
	 * Supprime de la base de données les cours de la liste de cours à
	 * supprimés.
	 * 
	 * @throws AAAException
	 */
	public void delete(Context context) throws AAAException {
		if (this.delete.isEmpty()) {// Rien à faire
			return;
		}// if

		ContentResolver resolver = context.getContentResolver();
		Uri lessonUri = AdeWebContract.Lesson.CONTENT_URI;

		// On recherche les cours annulés
		StringBuilder ids = new StringBuilder();
		final String separator = ",";//$NON-NLS-1$
		Date now = Calendar.getInstance().getTime();
		for (Lesson lesson : this.delete) {
			if (lesson.getStartDateTime().after(now)) {
				ids.append(separator);
				ids.append(lesson.getLessonId());
			}// if
		}// for

		// Mise à jour des cours annulés
		if (ids.length() > 0) {
			ids.delete(0, separator.length());
			ContentValues values = new ContentValues();
			values.put(AdeWebContract.Lesson.LESSON_DELETED, Integer.valueOf(1));
			String where = String.format("%s IN (%s)", //$NON-NLS-1$
					BaseColumns._ID,
					ids.toString());
			resolver.update(lessonUri, values, where, null);
		}// if

		// Suppression des cours passés
		String where = String.format("%s <= '%tY/%<tm/%<td'", //$NON-NLS-1$
				AdeWebContract.Lesson.LESSON_DATE,
				now);
		resolver.delete(lessonUri, where, null);
	}// delete()

	/**
	 * Insère, met à jour et supprime les cours de la base de données.
	 * 
	 * @throws AAAException
	 */
	public void updateSchedule(Context context) throws AAAException {
		this.delete(context);
		this.update(context);
		this.insert(context);
	}// updateSchedule(Database)

	/**
	 * Indique si un cours du futur proche (
	 * {@value ScheduleUpdater#CLOSE_INTERVAL} prochain jours) à été modifié.
	 * 
	 * @return {@code true} si un tel cours à été modifié, {@code false} sinon
	 */
	public boolean importantChange() {
		return !this.recent.isEmpty();
	}// importantChange()

	/**
	 * Indique si un seul et unique cours à été modifié (,ajouté ou supprimé).
	 * 
	 * @return {@code true} si un seul et unique cours à été modifié,
	 *         {@code false} sinon
	 */
	public boolean singleLessonChange() {
		return (this.recent.size() == 1);
	}// singleLessonChange()

	public int getNModifiedLessons() {
		return this.recent.size();
	}// getNModifiedLessons()

	public Lesson getModifiedLesson() {
		if (!this.recent.isEmpty()) {
			return this.recent.get(0);
		}// if
		return null;
	}// getModifiedLesson()

	public ChangeType getChangeType() {
		if (!this.recent.isEmpty()) {
			return this.getChangeType(this.recent.get(0));
		}// if
		return null;
	}// getChangeType()

	public ChangeType getChangeType(Lesson lesson) {
		if (this.insert.contains(lesson)) {
			return ChangeType.INSERT;
		}//
		if (this.update.contains(lesson)) {
			LessonHistory history = lesson.getHistory();
			if (!(history.getDate().equals(lesson.getDate())
					&& history.getStartHour().equals(lesson.getStartHour()) && history
					.getEndHour().equals(lesson.getEndHour()))) {
				return ChangeType.TIME_UPDATE;
			}// if
			if (!history.getRooms().equals(lesson.getRoom())) {
				return ChangeType.ROOM_UPDATE;
			}// if
		} else if (this.delete.contains(lesson)) {
			return ChangeType.DELETE;
		}// if
		return null;
	}// getChangeType(Lesson)

	public Lesson getModifiedLesson(int index) {
		if (index >= 0 && index < this.recent.size()) {
			return this.recent.get(index);
		}// if
		return null;
	}// getModifiedLesson(int)

	public static enum ChangeType {
		INSERT, TIME_UPDATE, ROOM_UPDATE, DELETE;
	}// enum ChangeType

}// class ScheduleUpdater